#!/usr/bin/env python
# -*- coding: utf-8 -*-

#
# This program is free software; you can redistribute it and/or modify  
# it under the terms of the GNU General Public License as published by  
# the Free Software Foundation; either version 2 of the License, or     
# (at your option) any later version.                                   
#                                                                         
# A copy of the license can be found in the license.txt file supplied   
# with this software or at: http://www.gnu.org/copyleft/gpl.html       
#


import time,socket,base64
import threading
import Queue as queue

import celtuce.net.ioloop as ioloop
import celtuce.net.iostream as iostream

import celtuce.help.logging as logging
import celtuce.help.url as urlparser
import celtuce.help.codec as codec

from celtuce.helper import getTimestamp
from celtuce.event import Event,ROUTE_GET,ROUTE_DIGEST,ROUTE_DEMAND,OP_FAILURE,TS_HEART_BEAT
from celtuce.response import Response, RC_TIMEOUT,RC_INTERNAL_ERROR


class   MetaAccessor(threading.Thread) :
    def __init__(self, meta_url) :
        threading.Thread.__init__(self)

        # such as tcp://host:port
        self._meta_url = meta_url
        parser = urlparser.URL(meta_url)
        self._meta_protocol = parser.protocol
        self._meta_host = parser.host
        self._meta_port = parser.port
        if 'tcp' == self._meta_protocol :
            self.__accessMetaServer = self.__accessMetaServer_tcp
        else :
            self.__accessMetaServer = None

        self._END_TAG = "\r\n\r\n"

        self._poll_timeout = 10
        self._event_queue = queue.Queue()
        self._would_stop = False
        self._logger = logging.getLogger('celtuce')
        ## __init__()

    def stop(self) :
        self._would_stop = True
        ## stop()

    def run(self) :
        while not self._would_stop :
            try :
                request = self._event_queue.get(True, self._poll_timeout)
            except queue.Empty :
                continue

            self.__handleRequest( request )
            self._event_queue.task_done()
            continue
        ## run()

    ####-----------------------------------------------------------####

    def getEventQueue(self) :
        return  self._event_queue
        ## getEventQueue()

    def postRequest(self, request) :
        if request :
            self._event_queue.put( request )
        ## postRequest()

    def __handleRequest(self, request) :
        source_queue = request.source_queue
        request.source_queue = None
        response = None
        
        #self._logger.debug('trying to handle request %s ...', str(request))

        if TS_HEART_BEAT == request.op :
            response = self.__accessMetaServer( request )

        elif ROUTE_DIGEST == request.op :
            response = self.getRouteDigest( request )
        elif ROUTE_GET == request.op :
            response = self.getRouteTable( request )
        elif ROUTE_DEMAND == request.op :
            response = self.demandRoute( request )

        else :
            self._logger.error('WHY not handle request %s', str(request))

        if response :
            source_queue.put( response )
        ## __handleReqeust()

    def getRouteTable(self, request) :
        response = self.__accessMetaServer( request )

        return response
        ## getRouteTable()

    def getRouteDigest(self, request) :
        response = self.__accessMetaServer( request )

        return response
        ## getRouteDigest()

    def demandRoute(self, request) :
        response = self.__accessMetaServer( request )

        return response
        ## demandRoute()


    def __accessMetaServer_tcp(self, request) :
        try :
            s = socket.socket(socket.AF_INET, socket.SOCK_STREAM, 0)
            s.connect( (self._meta_host, self._meta_port) )
        except :
            self._logger.excpt()
            response = Event()
            response.op = OP_FAILURE
            response.response_to = request.request_id
            response.body = 'fail to connect to meta-server (tcp://%s:%d)' % (self._meta_host, self._meta_port)
            return response

        self._io_loop = ioloop.IOLoop()
        self._stream = iostream.IOStream( s, self._io_loop )

        self.__wait_response_for = request.request_id
        request_str = codec.encode(request)
        self._stream.write( ''.join((request_str, self._END_TAG)) )
        self._stream.read_until(self._END_TAG, self.__onResponse)
        
        self._io_loop.start()

        try :
            s.close()
        except :
            pass

        self._io_loop = None
        return self._response
        ## __accessMetaServer_tcp()

    def __onResponse(self, response_str) :
        response = codec.decode( response_str )
        if response.response_to == self.__wait_response_for :
            self._response = response

            self._stream.close()
            self._io_loop.stop()
        else :
            # TODO : ignore other response and request
            self._stream.read_until(self._END_TAG, self.__onResponse)
        ## __onResponse()

    ## class MetaAccessor


#######################################################################

class   TabletServerProxy(threading.Thread) :
    def __init__(self, tablet_server_address) :
        threading.Thread.__init__(self)
        
        self._tablet_server_address = tablet_server_address
        self._tablet_server_host = tablet_server_address[0]
        self._tablet_server_port = tablet_server_address[1]

        self._END_TAG = "\r\n\r\n"

        self._poll_timeout = 10
        self._event_queue = queue.Queue()
        self._would_stop = False
        self._logger = logging.getLogger('celtuce')
        ## __init__()

    def stop(self) :
        self._would_stop = True
        ## stop()

    def run(self) :
        while not self._would_stop :
            try :
                request = self._event_queue.get(True, self._poll_timeout)
            except queue.Empty :
                continue

            self.__handleRequest( request )
            self._event_queue.task_done()
            continue
        ## run()

    ####-----------------------------------------------------------####

    def postRequest(self, request) :
        if request :
            self._event_queue.put( request )
        ## postRequest()

    def __handleRequest(self, request) :
        source_queue = request.source_queue
        request.source_queue = None
        response = None
        
        #self._logger.debug('trying to handle request %s ...', str(request))

        response = self.__accessTabletServer( request )

        if response :
            response.source_queue = self._tablet_server_address
            source_queue.put( response )
        ## __handleReqeust()

    def __accessTabletServer(self, request) :
        try :
            s = socket.socket(socket.AF_INET, socket.SOCK_STREAM, 0)
            s.connect( (self._tablet_server_host, self._tablet_server_port) )
        except :
            self._logger.excpt()
            response = Event()
            response.op = OP_FAILURE
            response.response_to = request.request_id
            response.body = 'fail to connect to table-server (tcp://%s:%d)' % (self._tablet_server_host, self._tablet_server_port)
            return response

        self._io_loop = ioloop.IOLoop()
        self._stream = iostream.IOStream( s, self._io_loop )

        self.__wait_response_for = request.request_id
        request_str = codec.encode(request)
        self._stream.write( ''.join((request_str, self._END_TAG)) )
        self._stream.read_until(self._END_TAG, self.__onResponse)
        
        self._io_loop.start()

        try :
            s.close()
        except :
            pass

        self._io_loop = None
        return self._response
        ## __accessMetaServer()

    def __onResponse(self, response_str) :
        response = codec.decode( response_str )
        if response.response_to == self.__wait_response_for :
            self._response = response

            self._stream.close()
            self._io_loop.stop()
        else :
            # TODO : ignore other response and request
            self._stream.read_until(self._END_TAG, self.__onResponse)
        ## __onResponse()

    ## class TabletServerProxy


class   DataAccessor(object) :
    def __init__(self) :
        self._proxies = {}
        ## __init__()

    def __del__(self) :
        self.shutdown()
        ## __del__()

    def shutdown(self) :
        for addr,proxy in self._proxies.iteritems() :
            if proxy :
                proxy.stop()
                if proxy.isAlive() :
                    proxy.join()
        ## shutdown()

    def postRequest(self, request, tablet_servers, timeout_ms) :
        for addr in tablet_servers :
            if addr not in self._proxies :
                proxy = TabletServerProxy( addr )
                proxy.start()
                self._proxies[ addr ] = proxy

            proxy = self._proxies[ addr ]
            proxy.postRequest( request )
        ## postRequest()

    ## class DataAccessor


